// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package modget

import (
	"fmt"
	"path/filepath"
	"regexp"
	"strings"
	"sync"

	"cmd/go/internal/base"
	"cmd/go/internal/modload"
	"cmd/go/internal/search"
	"cmd/go/internal/str"

	"golang.org/x/mod/module"
)

// 查询描述了一个命令行参数以及该参数可能解析的模块和/或包
// 。。
type query struct {
	// raw是原始参数，将在错误消息中打印。
	raw string

	// 如果任何
	rawVersion string

	// 模式是“@”之前参数的一部分（或者如果没有“@”则是整个参数
	// ），则rawVersion是对应于版本的raw部分，可能匹配包（首选）或
	// 模块（如果没有匹配的包）。
	// 
	// 对于表示-u 
	// /（“升级”）标志的合成查询，模式也可以是“-u”。
	pattern string

	// patternIsLocal表示模式是否仅限于匹配主模块的本地路径，例如绝对文件系统路径或路径
	// 以“./”开头。
	// 
	// 本地模式必须解析为主模块中的一个或多个包。
	patternIsLocal bool

	// version是“@”后面的参数部分，如果没有“@”，则是隐含的
	// “升级”或“补丁”。version指定要获取的
	// 模块版本。
	version string

	// 匹配通配符，如果非nil，则报告模式（必须是
	// 通配符）（带子字符串“…”），匹配给定的包或模块
	// 路径。
	matchWildcard func(path string) bool

	// CANMATCH通配符，如果非nil，则报告具有给定
	// 路径的模块是否可以在词汇上包含包匹配模式，该模式必须是
	// 通配符。
	canMatchWildcardInModule func(mPath string) bool

	// 冲突是第一个被确定为与此查询不兼容的查询。
	// 冲突迫使一个或多个与此查询匹配的模块与不匹配的
	// 版本冲突。
	conflict *query

	// 候选者是与模式匹配的路径（或
	// 包含与模式匹配的包）的备选方案集列表。查询可以通过
	// 从列表中的每个集合中选择一个选项来解决。
	// 
	// 路径文字查询只会产生一个集合：路径本身，
	// 可以解析为包路径或模块路径。
	// 
	// 通配符查询会为每个匹配的模块路径生成一个集合，每个
	// 模块的匹配版本至少包含一个匹配的
	// 包，以及（如果没有其他模块匹配）模式的一个候选集合
	// 如果在构建列表中没有识别出现有匹配项，则会生成一个集合。
	// 
	// 对模式“all”的查询会为主模块导入的每个包生成一个集合。
	// 
	// 对“-u”标志的特殊查询将为每个
	// 生成一个集合，否则将不受限制地提供升级。
	candidates   []pathSet
	candidatesMu sync.Mutex

	// pathSeen确保每个
	// 唯一路径只向查询添加一个路径集。
	pathSeen sync.Map

	// resolved包含一组模块，这些模块的版本由
	// 此查询按照确定的顺序确定。
	// 
	// 解析程序检查每个查询的候选集，为每个候选集解析一个
	// 模块，试图避免不同查询解析的版本之间的明显冲突。
	resolved []module.Version

	// 如果解析的模块提供至少一个
	// 包匹配q.pattern，则matchesPackages为真。
	matchesPackages bool
}

// 路径集描述了将特定路径解析为包和/或模块的可能选项。
type pathSet struct {
	// 路径是一个包（如果“all”或“-u”或非通配符）或模块（如果
	// 通配符）路径，可以通过添加此
	// 集中的任何模块来解析。对于到目前为止没有匹配包的通配符模式，路径是
	// 通配符模式本身。
	// 
	// 每个路径在查询的候选集中只能出现一次，并且路径是
	// 隐式添加到返回给pathOnce的每个路径集中。
	path string

	// pkgMods是一组零个或多个模块，每个模块都包含带有指定路径的
	// 包。由于导入必须是
	// 明确的要求，构建列表中只能有一个这样的模块，必须排除所有其他
	// 模块。
	pkgMods []module.Version

	// mod是零版本，或者是不包含任何与查询匹配的
	// 包，但模块路径本身
	// 与查询模式匹配的模块。
	// 
	// 我们将此模块与pkgMods分开跟踪，因为在其他条件相同的情况下，我们更喜欢将查询与包匹配，而不仅仅是模块。此外，与pkgMods中的模块不同，该模块并不固有地排除pkgMods中的任何其他模块。
	mod module.Version

	err error
}

// errSet返回包含给定错误的路径集。
func errSet(err error) pathSet { return pathSet{err: err} }

// newQuery返回从原始参数解析的新查询，
// 必须是path或path@version.
func newQuery(raw string) (*query, error) {
	pattern := raw
	rawVers := ""
	if i := strings.Index(raw, "@"); i >= 0 {
		pattern, rawVers = raw[:i], raw[i+1:]
		if strings.Contains(rawVers, "@") || rawVers == "" {
			return nil, fmt.Errorf("invalid module version syntax %q", raw)
		}
	}

	// 如果未指定版本后缀，则假设@upgrade。
	// 如果指定了-u=patch，则假定为@patch。
	version := rawVers
	if version == "" {
		if getU.version == "" {
			version = "upgrade"
		} else {
			version = getU.version
		}
	}

	q := &query{
		raw:            raw,
		rawVersion:     rawVers,
		pattern:        pattern,
		patternIsLocal: filepath.IsAbs(pattern) || search.IsRelativePath(pattern),
		version:        version,
	}
	if strings.Contains(q.pattern, "...") {
		q.matchWildcard = search.MatchPattern(q.pattern)
		q.canMatchWildcardInModule = search.TreeCanMatchPattern(q.pattern)
	}
	if err := q.validate(); err != nil {
		return q, err
	}
	return q, nil
}

// 如果q不合理且格式不正确，则validate报告非零错误。
func (q *query) validate() error {
	if q.patternIsLocal {
		if q.rawVersion != "" {
			return fmt.Errorf("can't request explicit version %q of path %q in main module", q.rawVersion, q.pattern)
		}
		return nil
	}

	if q.pattern == "all" {
		// 如果没有主模块，“all”就没有意义。
		if !modload.HasModRoot() {
			return fmt.Errorf(`cannot match "all": %v`, modload.ErrNoModRoot)
		}
		if !versionOkForMainModule(q.version) {
			// TODO（bcmills）：“all@none“对于
			// 请求我们删除所有模块需求，只留下主要的
			// 模块和标准库，这似乎是一个完全合理的方法。也许有一天我们应该实现它。
			return &modload.QueryUpgradesAllError{
				MainModules: modload.MainModules.Versions(),
				Query:       q.version,
			}
		}
	}

	if search.IsMetaPackage(q.pattern) && q.pattern != "all" {
		if q.pattern != q.raw {
			return fmt.Errorf("can't request explicit version of standard-library pattern %q", q.pattern)
		}
	}

	return nil
}

// String返回解析q的原始参数。
func (q *query) String() string { return q.raw }

// ResolvedString返回一个字符串，将m描述为q的已解析匹配。
func (q *query) ResolvedString(m module.Version) string {
	if m.Path != q.pattern {
		if m.Version != q.version {
			return fmt.Sprintf("%v (matching %s@%s)", m, q.pattern, q.version)
		}
		return fmt.Sprintf("%v (matching %v)", m, q)
	}
	if m.Version != q.version {
		return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, m.Version)
	}
	return q.String()
}

// isWildcard报告q是否是可以匹配多个路径的模式。
func (q *query) isWildcard() bool {
	return q.matchWildcard != nil || (q.patternIsLocal && strings.Contains(q.pattern, "..."))
}

// matchesPath报告给定路径是否与q.pattern匹配。
func (q *query) matchesPath(path string) bool {
	if q.matchWildcard != nil {
		return q.matchWildcard(path)
	}
	return path == q.pattern
}

// canMatchInModule报告给定的模块路径是否可能包含q.pattern。
func (q *query) canMatchInModule(mPath string) bool {
	if q.canMatchWildcardInModule != nil {
		return q.canMatchWildcardInModule(mPath)
	}
	return str.HasPathPrefix(q.pattern, mPath)
}

// pathOnce调用f为给定路径生成路径集，如果仍然需要，则为
// 生成路径集。
// 
// 请注意，与同步不同。有一次，pathOnce不保证给定路径的并发
// 调用在返回时已完成。
// 
// pathOnce对于多个Goroutine并发使用是安全的，但请注意，
// 多个并发调用将导致以
// 不确定的顺序添加集合。
func (q *query) pathOnce(path string, f func() pathSet) {
	if _, dup := q.pathSeen.LoadOrStore(path, nil); dup {
		return
	}

	cs := f()

	if len(cs.pkgMods) > 0 || cs.mod != (module.Version{}) || cs.err != nil {
		cs.path = path
		q.candidatesMu.Lock()
		q.candidates = append(q.candidates, cs)
		q.candidatesMu.Unlock()
	}
}

// 报告错误日志使用base简洁。错误。
func reportError(q *query, err error) {
	errStr := err.Error()

	// 如果err已经提到q的所有相关部分，只需将err记录到
	// 减少口吃。否则，记录q和err。
	// 
	// TODO（bcmills）：使用错误。至于解压这些错误，而不是用正则表达式解析
	// 字符串。

	patternRE := regexp.MustCompile("(?m)(?:[ \t(\"`]|^)" + regexp.QuoteMeta(q.pattern) + "(?:[ @:;)\"`]|$)")
	if patternRE.MatchString(errStr) {
		if q.rawVersion == "" {
			base.Errorf("go: %s", errStr)
			return
		}

		versionRE := regexp.MustCompile("(?m)(?:[ @(\"`]|^)" + regexp.QuoteMeta(q.version) + "(?:[ :;)\"`]|$)")
		if versionRE.MatchString(errStr) {
			base.Errorf("go: %s", errStr)
			return
		}
	}

	if qs := q.String(); qs != "" {
		base.Errorf("go: %s: %s", qs, errStr)
	} else {
		base.Errorf("go: %s", errStr)
	}
}

func reportConflict(pq *query, m module.Version, conflict versionReason) {
	if pq.conflict != nil {
		// 我们已经报告了建议查询的冲突。
		// 即使有其他冲突，也不要再报告。
		return
	}
	pq.conflict = conflict.reason

	proposed := versionReason{
		version: m.Version,
		reason:  pq,
	}
	if pq.isWildcard() && !conflict.reason.isWildcard() {
		// 首选先报告特定路径，然后报告通配符。
		proposed, conflict = conflict, proposed
	}
	reportError(pq, &conflictError{
		mPath:    m.Path,
		proposed: proposed,
		conflict: conflict,
	})
}

type conflictError struct {
	mPath    string
	proposed versionReason
	conflict versionReason
}

func (e *conflictError) Error() string {
	argStr := func(q *query, v string) string {
		if v != q.version {
			return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, v)
		}
		return q.String()
	}

	pq := e.proposed.reason
	rq := e.conflict.reason
	modDetail := ""
	if e.mPath != pq.pattern {
		modDetail = fmt.Sprintf("for module %s, ", e.mPath)
	}

	return fmt.Sprintf("%s%s conflicts with %s",
		modDetail,
		argStr(pq, e.proposed.version),
		argStr(rq, e.conflict.version))
}

func versionOkForMainModule(version string) bool {
	return version == "upgrade" || version == "patch"
}
